/*-------------------------------------------------------------------------
 *
 * jsonb_gin.c
 *	 jsonb 的 GIN 支持函数
 *
 * Copyright (c) 2014-2022, PostgreSQL Global Development Group
 *
 * 我们提供两个用于 jsonb 索引的操作类：jsonb_ops 和 jsonb_path_ops。
 * 有关它们的描述，请参见 json.sgml 和 jsonb.h 中的注释。
 *
 * 这些运算符支持“jsonb @? jsonpath”和“jsonb @@ jsonpath”等。包含这些运算符的表达式之间的关系易于表达。
 *
 *	jb @? 'path' <=> jb @@ 'EXISTS(path)'
 *	jb @@ 'expr' <=> jb @? '$ ? (expr)'
 *
 * 因此，我们将仅考虑 @@ 运算符，而对于 @? 运算符同样适用于 jb @@ 'EXISTS(path)'。
 *
 * jsonpath 查询提取的结果是一个树，其中叶子节点是索引条目，非叶子节点是 AND/OR 逻辑表达式。基本上，我们从 jsonpath 中提取以下语句：
 *
 *	1) "accessors_chain = const",
 *	2) "EXISTS(accessors_chain)"。
 *
 * 访问链可以由 .key、[*] 和 [index] 访问器组成。jsonb_ops 还额外支持 .* 和 .**。
 *
 * 目前，jsonb_ops 和 jsonb_path_ops 仅支持第一类语句。jsonb_ops 可能还支持第二类语句，但由于我们没有从访问者链提取的统计信息，因此它们可能是非选择性的。因此，我们选择不混淆优化器，并完全跳过第二类语句。未来的版本可能会有所改变。
 *
 * 在 jsonb_ops 中，第一类语句被分割为 AND 连接的键与常量的表达式。有时常量可以同时被解释为 jsonb_ops 中的值或键。那么第一类语句被分解为以下表达式。
 *
 *	key1 AND key2 AND ... AND keyN AND (const_as_value OR const_as_key)
 *
 * jsonb_path_ops 将每个第一类语句转换成下面的单个哈希条目。
 *
 *	HASH(key1, key2, ... , keyN, const)
 *
 * 尽管 jsonb_ops 和 jsonb_path_ops 不支持第二类语句，但如果能从其过滤器中提取出第一类语句，EXISTS(path) 表达式仍然可能被支持。
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/jsonb_gin.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/gin.h"
#include "access/stratnum.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/jsonb.h"
#include "utils/jsonpath.h"
#include "utils/varlena.h"

typedef struct PathHashStack
{
	uint32		hash;
	struct PathHashStack *parent;
} PathHashStack;

/* GIN 条目的缓冲区 */
typedef struct GinEntries
{
	Datum	   *buf;
	int			count;
	int			allocated;
} GinEntries;

typedef enum JsonPathGinNodeType
{
	JSP_GIN_OR,
	JSP_GIN_AND,
	JSP_GIN_ENTRY
} JsonPathGinNodeType;

typedef struct JsonPathGinNode JsonPathGinNode;

/* jsonpath 表达式树中的节点 */
struct JsonPathGinNode
{
	JsonPathGinNodeType type;
	union
	{
		int			nargs;		/* 适用于 OR 和 AND 节点 */
		int			entryIndex; /* GinEntries 数组中的索引，适用于条目输出后的 ENTRY 节点 */
		Datum		entryDatum; /* 路径哈希或键名/标量，适用于条目输出前的 ENTRY 节点 */
	}			val;
	JsonPathGinNode *args[FLEXIBLE_ARRAY_MEMBER];	/* 适用于 OR 和 AND 节点 */
};

/*
 * 从 jsonpath 项目提取的 jsonb_ops 条目。相应的路径项可能是：'.key'、'.*'、'.**'、'[index]' 或 '[*]'。
 * 条目类型存储在 'type' 字段中。
 */
typedef struct JsonPathGinPathItem
{
	struct JsonPathGinPathItem *parent;
	Datum		keyName;		/* 键名（对于 '.key' 路径项）或 NULL */
	JsonPathItemType type;		/* jsonpath 项的类型 */
} JsonPathGinPathItem;

/* 提取的 json 路径的 GIN 表示 */
typedef union JsonPathGinPath
{
	JsonPathGinPathItem *items; /* 路径项列表 (jsonb_ops) */
	uint32		hash;			/* 路径的哈希 (jsonb_path_ops) */
} JsonPathGinPath;

typedef struct JsonPathGinContext JsonPathGinContext;

/* 回调，存储路径项信息到 JsonPathGinPath */
typedef bool (*JsonPathGinAddPathItemFunc) (JsonPathGinPath *path,
											JsonPathItem *jsp);

/* 
 * 回调，从第一种类型的语句提取节点集合
 * (标量 != NULL) 或第二种类型的语句 (标量 == NULL)。
 */
typedef List *(*JsonPathGinExtractNodesFunc) (JsonPathGinContext *cxt,
											  JsonPathGinPath path,
											  JsonbValue *scalar,
											  List *nodes);

/* jsonpath 条目提取的上下文 */
struct JsonPathGinContext
{
	JsonPathGinAddPathItemFunc add_path_item;
	JsonPathGinExtractNodesFunc extract_nodes;
	bool		lax;
};

static Datum fc_make_text_key(char fc_flag, const char *fc_str, int fc_len);
static Datum fc_make_scalar_key(const JsonbValue *fc_scalarVal, bool fc_is_key);

static JsonPathGinNode *fc_extract_jsp_bool_expr(JsonPathGinContext *fc_cxt,
											  JsonPathGinPath fc_path, JsonPathItem *fc_jsp, bool fc_not);


/* 初始化 GinEntries 结构 */
static void fc_init_gin_entries(GinEntries *fc_entries, int fc_preallocated)
{
	fc_entries->allocated = fc_preallocated;
	fc_entries->buf = fc_preallocated ? palloc(sizeof(Datum) * fc_preallocated) : NULL;
	fc_entries->count = 0;
}

/* 将新条目添加到 GinEntries */
static int add_gin_entry(GinEntries *fc_entries, Datum fc_entry)
{
	int			fc_id = fc_entries->count;

	if (fc_entries->count >= fc_entries->allocated)
	{
		if (fc_entries->allocated)
		{
			fc_entries->allocated *= 2;
			fc_entries->buf = repalloc(fc_entries->buf,
									sizeof(Datum) * fc_entries->allocated);
		}
		else
		{
			fc_entries->allocated = 8;
			fc_entries->buf = palloc(sizeof(Datum) * fc_entries->allocated);
		}
	}

	fc_entries->buf[fc_entries->count++] = fc_entry;

	return fc_id;
}

/*
 * jsonb_ops GIN 操作类支持函数
 */

Datum gin_compare_jsonb(PG_FUNCTION_ARGS)
{
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg2 = PG_GETARG_TEXT_PP(1);
	int32		fc_result;
	char	   *fc_a1p,
			   *fc_a2p;
	int			fc_len1,
				fc_len2;

	fc_a1p = VARDATA_ANY(fc_arg1);
	fc_a2p = VARDATA_ANY(fc_arg2);

	fc_len1 = VARSIZE_ANY_EXHDR(fc_arg1);
	fc_len2 = VARSIZE_ANY_EXHDR(fc_arg2);

	/* 像 bttextcmp 一样比较文本，但始终使用 C 排序 */
	fc_result = varstr_cmp(fc_a1p, fc_len1, fc_a2p, fc_len2, C_COLLATION_OID);

	PG_FREE_IF_COPY(fc_arg1, 0);
	PG_FREE_IF_COPY(fc_arg2, 1);

	PG_RETURN_INT32(fc_result);
}

Datum gin_extract_jsonb(PG_FUNCTION_ARGS)
{
	Jsonb	   *fc_jb = (Jsonb *) PG_GETARG_JSONB_P(0);
	int32	   *fc_nentries = (int32 *) PG_GETARG_POINTER(1);
	int			fc_total = JB_ROOT_COUNT(fc_jb);
	JsonbIterator *fc_it;
	JsonbValue	fc_v;
	JsonbIteratorToken fc_r;
	GinEntries	fc_entries;

	/* 如果根级别为空，我们肯定没有键 */
	if (fc_total == 0)
	{
		*fc_nentries = 0;
		PG_RETURN_POINTER(NULL);
	}

	/* 否则，使用 2 * 根计数作为结果大小的初步估计 */
	fc_init_gin_entries(&fc_entries, 2 * fc_total);

	fc_it = JsonbIteratorInit(&fc_jb->root);

	while ((fc_r = JsonbIteratorNext(&fc_it, &fc_v, false)) != WJB_DONE)
	{
		switch (fc_r)
		{
			case WJB_KEY:
				add_gin_entry(&fc_entries, fc_make_scalar_key(&fc_v, true));
				break;
			case WJB_ELEM:
				/* 假装字符串数组元素是键，请参阅 jsonb.h */
				add_gin_entry(&fc_entries, fc_make_scalar_key(&fc_v, fc_v.type == jbvString));
				break;
			case WJB_VALUE:
				add_gin_entry(&fc_entries, fc_make_scalar_key(&fc_v, false));
				break;
			default:
				/* 我们可以忽略结构项目 */
				break;
		}
	}

	*fc_nentries = fc_entries.count;

	PG_RETURN_POINTER(fc_entries.buf);
}

/* 将 JsonPathGinPathItem 附加到 JsonPathGinPath (jsonb_ops) */
static bool fc_jsonb_ops__add_path_item(JsonPathGinPath *fc_path, JsonPathItem *fc_jsp)
{
	JsonPathGinPathItem *fc_pentry;
	Datum		fc_keyName;

	switch (fc_jsp->type)
	{
		case jpiRoot:
			fc_path->items = NULL; /* 重置路径 */
			return true;

		case jpiKey:
			{
				int			fc_len;
				char	   *fc_key = jspGetString(fc_jsp, &fc_len);

				fc_keyName = fc_make_text_key(JGINFLAG_KEY, fc_key, fc_len);
				break;
			}

		case jpiAny:
		case jpiAnyKey:
		case jpiAnyArray:
		case jpiIndexArray:
			fc_keyName = PointerGetDatum(NULL);
			break;

		default:
			/* 像项目方法这样的其他路径项不受支持 */
			return false;
	}

	fc_pentry = palloc(sizeof(*fc_pentry));

	fc_pentry->type = fc_jsp->type;
	fc_pentry->keyName = fc_keyName;
	fc_pentry->parent = fc_path->items;

	fc_path->items = fc_pentry;

	return true;
}

/* 将现有路径哈希与下一个键哈希结合 (jsonb_path_ops) */
static bool fc_jsonb_path_ops__add_path_item(JsonPathGinPath *fc_path, JsonPathItem *fc_jsp)
{
	switch (fc_jsp->type)
	{
		case jpiRoot:
			fc_path->hash = 0;		/* 重置路径哈希 */
			return true;

		case jpiKey:
			{
				JsonbValue	fc_jbv;

				fc_jbv.type = jbvString;
				fc_jbv.val.string.val = jspGetString(fc_jsp, &fc_jbv.val.string.len);

				JsonbHashScalarValue(&fc_jbv, &fc_path->hash);
				return true;
			}

		case jpiIndexArray:
		case jpiAnyArray:
			return true;		/* 路径哈希保持不变 */

		default:
			/* 其他项目（通配符路径，项目方法）不受支持 */
			return false;
	}
}

static JsonPathGinNode * fc_make_jsp_entry_node(Datum fc_entry)
{
	JsonPathGinNode *fc_node = palloc(offsetof(JsonPathGinNode, args));

	fc_node->type = JSP_GIN_ENTRY;
	fc_node->val.entryDatum = fc_entry;

	return fc_node;
}

static JsonPathGinNode * fc_make_jsp_entry_node_scalar(JsonbValue *fc_scalar, bool fc_iskey)
{
	return fc_make_jsp_entry_node(fc_make_scalar_key(fc_scalar, fc_iskey));
}

static JsonPathGinNode * fc_make_jsp_expr_node(JsonPathGinNodeType fc_type, int fc_nargs)
{
	JsonPathGinNode *fc_node = palloc(offsetof(JsonPathGinNode, args) +
								   sizeof(fc_node->args[0]) * fc_nargs);

	fc_node->type = fc_type;
	fc_node->val.nargs = fc_nargs;

	return fc_node;
}

static JsonPathGinNode * fc_make_jsp_expr_node_args(JsonPathGinNodeType fc_type, List *fc_args)
{
	JsonPathGinNode *fc_node = fc_make_jsp_expr_node(fc_type, list_length(fc_args));
	ListCell   *fc_lc;
	int			fc_i = 0;

	foreach(fc_lc, fc_args)
		fc_node->args[fc_i++] = lfirst(fc_lc);

	return fc_node;
}

static JsonPathGinNode * fc_make_jsp_expr_node_binary(JsonPathGinNodeType fc_type,
						  JsonPathGinNode *fc_arg1, JsonPathGinNode *fc_arg2)
{
	JsonPathGinNode *fc_node = fc_make_jsp_expr_node(fc_type, 2);

	fc_node->args[0] = fc_arg1;
	fc_node->args[1] = fc_arg2;

	return fc_node;
}

/* 从 jsonpath 附加节点列表 (jsonb_ops)。 */
static List * fc_jsonb_ops__extract_nodes(JsonPathGinContext *fc_cxt, JsonPathGinPath fc_path,
						 JsonbValue *fc_scalar, List *fc_nodes)
{
	JsonPathGinPathItem *fc_pentry;

	if (fc_scalar)
	{
		JsonPathGinNode *fc_node;

		/* 
	 * 仅在提供标量时附加路径条目节点。有关详细信息，请参见标题
	 * 注释。
	 */
		for (fc_pentry = fc_path.items; fc_pentry; fc_pentry = fc_pentry->parent)
		{
			if (fc_pentry->type == jpiKey) /* 仅键被索引 */
				fc_nodes = lappend(fc_nodes, fc_make_jsp_entry_node(fc_pentry->keyName));
		}

		/* 为相等查询附加标量节点。 */
		if (fc_scalar->type == jbvString)
		{
			JsonPathGinPathItem *fc_last = fc_path.items;
			GinTernaryValue fc_key_entry;

			/* 
			 * 假设 jsonb_ops 将字符串数组元素解释为
			 * 键，我们可以提取键或非键条目，甚至两者。在
			 * 后一种情况下我们创建 OR-节点。这在宽松模式下
			 * 是可能的，其中数组自动解包，或在严格模式下发生
			 * jpiAny 项目。
			 */

			if (fc_cxt->lax)
				fc_key_entry = GIN_MAYBE;
			else if (!fc_last)		/* root ($) */
				fc_key_entry = GIN_FALSE;
			else if (fc_last->type == jpiAnyArray || fc_last->type == jpiIndexArray)
				fc_key_entry = GIN_TRUE;
			else if (fc_last->type == jpiAny)
				fc_key_entry = GIN_MAYBE;
			else
				fc_key_entry = GIN_FALSE;

			if (fc_key_entry == GIN_MAYBE)
			{
				JsonPathGinNode *fc_n1 = fc_make_jsp_entry_node_scalar(fc_scalar, true);
				JsonPathGinNode *fc_n2 = fc_make_jsp_entry_node_scalar(fc_scalar, false);

				fc_node = fc_make_jsp_expr_node_binary(JSP_GIN_OR, fc_n1, fc_n2);
			}
			else
			{
				fc_node = fc_make_jsp_entry_node_scalar(fc_scalar,
												  fc_key_entry == GIN_TRUE);
			}
		}
		else
		{
			fc_node = fc_make_jsp_entry_node_scalar(fc_scalar, false);
		}

		fc_nodes = lappend(fc_nodes, fc_node);
	}

	return fc_nodes;
}

/* 从 jsonpath 附加节点列表 (jsonb_path_ops)。 */
static List * fc_jsonb_path_ops__extract_nodes(JsonPathGinContext *fc_cxt, JsonPathGinPath fc_path,
							  JsonbValue *fc_scalar, List *fc_nodes)
{
	if (fc_scalar)
	{
		/* 为相等查询附加路径哈希节点 */
		uint32		fc_hash = fc_path.hash;

		JsonbHashScalarValue(fc_scalar, &fc_hash);

		return lappend(fc_nodes,
					   fc_make_jsp_entry_node(UInt32GetDatum(fc_hash)));
	}
	else
	{
		/* jsonb_path_ops 不支持 EXISTS 查询 => 没有什么可附加的 */
		return fc_nodes;
	}
}

/* 
 * 提取需要由调用者进行 AND 操作的表达式节点列表。
 * 提取的表达式是 'path == scalar' 如果 'scalar' 非 NULL，并且
 * 'EXISTS(path)' 否则。
 */
static List * fc_extract_jsp_path_expr_nodes(JsonPathGinContext *fc_cxt, JsonPathGinPath fc_path,
							JsonPathItem *fc_jsp, JsonbValue *fc_scalar)
{
	JsonPathItem fc_next;
	List	   *fc_nodes = NIL;

	for (;;)
	{
		switch (fc_jsp->type)
		{
			case jpiCurrent:
				break;

			case jpiFilter:
				{
					JsonPathItem fc_arg;
					JsonPathGinNode *fc_filter;

					jspGetArg(fc_jsp, &fc_arg);

					fc_filter = fc_extract_jsp_bool_expr(fc_cxt, fc_path, &fc_arg, false);

					if (fc_filter)
						fc_nodes = lappend(fc_nodes, fc_filter);

					break;
				}

			default:
				if (!fc_cxt->add_path_item(&fc_path, fc_jsp))

					/* 
					 * 该路径不被索引操作类支持，仅返回
					 * 提取的过滤节点。
					 */
					return fc_nodes;
				break;
		}

		if (!jspGetNext(fc_jsp, &fc_next))
			break;

		fc_jsp = &fc_next;
	}

	/* 
	 * 将路径表达式本身的节点附加到已经提取的
	 * 过滤节点列表中。
	 */ 
	return fc_cxt->extract_nodes(fc_cxt, fc_path, fc_scalar, fc_nodes);
}

/*
 * 从以下 jsonpath 路径表达式中提取一个表达式节点：
 *   EXISTS(jsp)    （当 'scalar' 为 NULL 时）
 *   jsp == scalar  （当 'scalar' 不为 NULL 时）。
 *
 * 当前路径 (@) 被传递到 'path' 中。
 */
static JsonPathGinNode * fc_extract_jsp_path_expr(JsonPathGinContext *fc_cxt, JsonPathGinPath fc_path,
					  JsonPathItem *fc_jsp, JsonbValue *fc_scalar)
{
	/* 提取要进行 AND 运算的节点列表 */
	List	   *fc_nodes = fc_extract_jsp_path_expr_nodes(fc_cxt, fc_path, fc_jsp, fc_scalar);

	if (list_length(fc_nodes) <= 0)
		/* 未提取到节点 => 该路径需要完全扫描 */
		return NULL;

	if (list_length(fc_nodes) == 1)
		return linitial(fc_nodes); /* 避免额外的 AND 节点 */

	/* 为带有过滤器的路径构造 AND 节点 */
	return fc_make_jsp_expr_node_args(JSP_GIN_AND, fc_nodes);
}

/* 递归提取布尔 jsonpath 表达式中的节点。 */
static JsonPathGinNode * fc_extract_jsp_bool_expr(JsonPathGinContext *fc_cxt, JsonPathGinPath fc_path,
					  JsonPathItem *fc_jsp, bool fc_not)
{
	check_stack_depth();

	switch (fc_jsp->type)
	{
		case jpiAnd:			/* expr && expr */
		case jpiOr:				/* expr || expr */
			{
				JsonPathItem fc_arg;
				JsonPathGinNode *fc_larg;
				JsonPathGinNode *fc_rarg;
				JsonPathGinNodeType fc_type;

				jspGetLeftArg(fc_jsp, &fc_arg);
				fc_larg = fc_extract_jsp_bool_expr(fc_cxt, fc_path, &fc_arg, fc_not);

				jspGetRightArg(fc_jsp, &fc_arg);
				fc_rarg = fc_extract_jsp_bool_expr(fc_cxt, fc_path, &fc_arg, fc_not);

				if (!fc_larg || !fc_rarg)
				{
					if (fc_jsp->type == jpiOr)
						return NULL;

					return fc_larg ? fc_larg : fc_rarg;
				}

				fc_type = fc_not ^ (fc_jsp->type == jpiAnd) ? JSP_GIN_AND : JSP_GIN_OR;

				return fc_make_jsp_expr_node_binary(fc_type, fc_larg, fc_rarg);
			}

		case jpiNot:			/* !expr  */
			{
				JsonPathItem fc_arg;

				jspGetArg(fc_jsp, &fc_arg);

				/* 提取子表达式并反转 'not' 标志 */
				return fc_extract_jsp_bool_expr(fc_cxt, fc_path, &fc_arg, !fc_not);
			}

		case jpiExists:			/* EXISTS(path) */
			{
				JsonPathItem fc_arg;

				if (fc_not)
					return NULL;	/* 不支持 NOT EXISTS */

				jspGetArg(fc_jsp, &fc_arg);

				return fc_extract_jsp_path_expr(fc_cxt, fc_path, &fc_arg, NULL);
			}

		case jpiNotEqual:

			/*
			 * 'not' == true 的情况在这里不被支持，因为 '!(path !=
			 * scalar)' 在一般情况下并不等价于 'path == scalar'，因为序列比较语义：'path == scalar'
			 * === 'EXISTS (path, @ == scalar)'，'!(path != scalar)' ===
			 * 'FOR_ALL(path, @ == scalar)'。因此，我们应该将 '!(path
			 * != scalar)' 转换为 GIN 查询 'path == scalar || EMPTY(path)'，但
			 * 'EMPTY(path)' 查询不被两个 jsonb
			 * opclasses 支持。然而，在严格模式下，如果路径可以返回一个确切的项（不包含通配符访问器或像 .keyvalue()
			 * 等项目方法），我们可以省略 'EMPTY(path)'
			 * 部分。
			 */
			return NULL;

		case jpiEqual:			/* path == scalar */
			{
				JsonPathItem fc_left_item;
				JsonPathItem fc_right_item;
				JsonPathItem *fc_path_item;
				JsonPathItem *fc_scalar_item;
				JsonbValue	fc_scalar;

				if (fc_not)
					return NULL;

				jspGetLeftArg(fc_jsp, &fc_left_item);
				jspGetRightArg(fc_jsp, &fc_right_item);

				if (jspIsScalar(fc_left_item.type))
				{
					fc_scalar_item = &fc_left_item;
					fc_path_item = &fc_right_item;
				}
				else if (jspIsScalar(fc_right_item.type))
				{
					fc_scalar_item = &fc_right_item;
					fc_path_item = &fc_left_item;
				}
				else
					return NULL;	/* 至少一个操作数应为标量 */

				switch (fc_scalar_item->type)
				{
					case jpiNull:
						fc_scalar.type = jbvNull;
						break;
					case jpiBool:
						fc_scalar.type = jbvBool;
						fc_scalar.val.boolean = !!*fc_scalar_item->content.value.data;
						break;
					case jpiNumeric:
						fc_scalar.type = jbvNumeric;
						fc_scalar.val.numeric =
							(Numeric) fc_scalar_item->content.value.data;
						break;
					case jpiString:
						fc_scalar.type = jbvString;
						fc_scalar.val.string.val = fc_scalar_item->content.value.data;
						fc_scalar.val.string.len =
							fc_scalar_item->content.value.datalen;
						break;
					default:
						elog(ERROR, "invalid scalar jsonpath item type: %d",
							 fc_scalar_item->type);
						return NULL;
				}

				return fc_extract_jsp_path_expr(fc_cxt, fc_path, fc_path_item, &fc_scalar);
			}

		default:
			return NULL;		/* 不是布尔表达式 */
	}
}

/* 递归发出在节点树中找到的所有 GIN 条目 */
static void fc_emit_jsp_gin_entries(JsonPathGinNode *fc_node, GinEntries *fc_entries)
{
	check_stack_depth();

	switch (fc_node->type)
	{
		case JSP_GIN_ENTRY:
			/* 用数组中的索引替换数据项 */
			fc_node->val.entryIndex = add_gin_entry(fc_entries, fc_node->val.entryDatum);
			break;

		case JSP_GIN_OR:
		case JSP_GIN_AND:
			{
				int			fc_i;

				for (fc_i = 0; fc_i < fc_node->val.nargs; fc_i++)
					fc_emit_jsp_gin_entries(fc_node->args[fc_i], fc_entries);

				break;
			}
	}
}

/*
 * 递归提取 jsonpath 查询中的 GIN 条目。
 * 根表达式节点被放入 (*extra_data)[0] 中。
 */
static Datum *
fc_extract_jsp_query(JsonPath *fc_jp, StrategyNumber fc_strat, bool fc_pathOps,
				  int32 *fc_nentries, Pointer **fc_extra_data)
{
	JsonPathGinContext fc_cxt;
	JsonPathItem fc_root;
	JsonPathGinNode *fc_node;
	JsonPathGinPath fc_path = {0};
	GinEntries	fc_entries = {0};

	fc_cxt.lax = (fc_jp->header & JSONPATH_LAX) != 0;

	if (fc_pathOps)
	{
		fc_cxt.add_path_item = fc_jsonb_path_ops__add_path_item;
		fc_cxt.extract_nodes = fc_jsonb_path_ops__extract_nodes;
	}
	else
	{
		fc_cxt.add_path_item = fc_jsonb_ops__add_path_item;
		fc_cxt.extract_nodes = fc_jsonb_ops__extract_nodes;
	}

	jspInit(&fc_root, fc_jp);

	fc_node = fc_strat == JsonbJsonpathExistsStrategyNumber
		? fc_extract_jsp_path_expr(&fc_cxt, fc_path, &fc_root, NULL)
		: fc_extract_jsp_bool_expr(&fc_cxt, fc_path, &fc_root, false);

	if (!fc_node)
	{
		*fc_nentries = 0;
		return NULL;
	}

	fc_emit_jsp_gin_entries(fc_node, &fc_entries);

	*fc_nentries = fc_entries.count;
	if (!*fc_nentries)
		return NULL;

	*fc_extra_data = palloc0(sizeof(**fc_extra_data) * fc_entries.count);
	**fc_extra_data = (Pointer) fc_node;

	return fc_entries.buf;
}

/*
 * 递归执行 jsonpath 表达式。
 * 'check' 是一个 bool[] 或 GinTernaryValue[]，具体取决于 'ternary' 标志。
 */
static GinTernaryValue fc_execute_jsp_gin_node(JsonPathGinNode *fc_node, void *fc_check, bool fc_ternary)
{
	GinTernaryValue fc_res;
	GinTernaryValue fc_v;
	int			fc_i;

	switch (fc_node->type)
	{
		case JSP_GIN_AND:
			fc_res = GIN_TRUE;
			for (fc_i = 0; fc_i < fc_node->val.nargs; fc_i++)
			{
				fc_v = fc_execute_jsp_gin_node(fc_node->args[fc_i], fc_check, fc_ternary);
				if (fc_v == GIN_FALSE)
					return GIN_FALSE;
				else if (fc_v == GIN_MAYBE)
					fc_res = GIN_MAYBE;
			}
			return fc_res;

		case JSP_GIN_OR:
			fc_res = GIN_FALSE;
			for (fc_i = 0; fc_i < fc_node->val.nargs; fc_i++)
			{
				fc_v = fc_execute_jsp_gin_node(fc_node->args[fc_i], fc_check, fc_ternary);
				if (fc_v == GIN_TRUE)
					return GIN_TRUE;
				else if (fc_v == GIN_MAYBE)
					fc_res = GIN_MAYBE;
			}
			return fc_res;

		case JSP_GIN_ENTRY:
			{
				int			fc_index = fc_node->val.entryIndex;

				if (fc_ternary)
					return ((GinTernaryValue *) fc_check)[fc_index];
				else
					return ((bool *) fc_check)[fc_index] ? GIN_TRUE : GIN_FALSE;
			}

		default:
			elog(ERROR, "invalid jsonpath gin node type: %d", fc_node->type);
			return GIN_FALSE;	/* 保持编译器安静 */
	}
}

Datum gin_extract_jsonb_query(PG_FUNCTION_ARGS)
{
	int32	   *fc_nentries = (int32 *) PG_GETARG_POINTER(1);
	StrategyNumber fc_strategy = PG_GETARG_UINT16(2);
	int32	   *fc_searchMode = (int32 *) PG_GETARG_POINTER(6);
	Datum	   *fc_entries;

	if (fc_strategy == JsonbContainsStrategyNumber)
	{
		/* 查询是一个 jsonb，因此只需应用 gin_extract_jsonb... */
		fc_entries = (Datum *)
			DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb,
												PG_GETARG_DATUM(0),
												PointerGetDatum(fc_nentries)));
		/* ...尽管 "contains {}" 需要完全索引扫描 */
		if (*fc_nentries == 0)
			*fc_searchMode = GIN_SEARCH_MODE_ALL;
	}
	else if (fc_strategy == JsonbExistsStrategyNumber)
	{
		/* 查询是一个文本字符串，我们将其视为键 */
		text	   *fc_query = PG_GETARG_TEXT_PP(0);

		*fc_nentries = 1;
		fc_entries = (Datum *) palloc(sizeof(Datum));
		fc_entries[0] = fc_make_text_key(JGINFLAG_KEY,
								   VARDATA_ANY(fc_query),
								   VARSIZE_ANY_EXHDR(fc_query));
	}
	else if (fc_strategy == JsonbExistsAnyStrategyNumber ||
			 fc_strategy == JsonbExistsAllStrategyNumber)
	{
		/* 查询是一个文本数组；每个元素被视为键 */
		ArrayType  *fc_query = PG_GETARG_ARRAYTYPE_P(0);
		Datum	   *fc_key_datums;
		bool	   *fc_key_nulls;
		int			fc_key_count;
		int			fc_i,
					fc_j;

		deconstruct_array(fc_query,
						  TEXTOID, -1, false, TYPALIGN_INT,
						  &fc_key_datums, &fc_key_nulls, &fc_key_count);

		fc_entries = (Datum *) palloc(sizeof(Datum) * fc_key_count);

		for (fc_i = 0, fc_j = 0; fc_i < fc_key_count; fc_i++)
		{
			/* 数组中的 Null 被忽略 */
			if (fc_key_nulls[fc_i])
				continue;
			/* 我们依赖于数组元素未被压缩 */
			fc_entries[fc_j++] = fc_make_text_key(JGINFLAG_KEY,
										 VARDATA_ANY(fc_key_datums[fc_i]),
										 VARSIZE_ANY_EXHDR(fc_key_datums[fc_i]));
		}

		*fc_nentries = fc_j;
		/* 无键的ExistsAll应该匹配所有内容 */
		if (fc_j == 0 && fc_strategy == JsonbExistsAllStrategyNumber)
			*fc_searchMode = GIN_SEARCH_MODE_ALL;
	}
	else if (fc_strategy == JsonbJsonpathPredicateStrategyNumber ||
			 fc_strategy == JsonbJsonpathExistsStrategyNumber)
	{
		JsonPath   *fc_jp = PG_GETARG_JSONPATH_P(0);
		Pointer   **fc_extra_data = (Pointer **) PG_GETARG_POINTER(4);

		fc_entries = fc_extract_jsp_query(fc_jp, fc_strategy, false, fc_nentries, fc_extra_data);

		if (!fc_entries)
			*fc_searchMode = GIN_SEARCH_MODE_ALL;
	}
	else
	{
		elog(ERROR, "unrecognized strategy number: %d", fc_strategy);
		fc_entries = NULL;			/* 保持编译器安静 */
	}

	PG_RETURN_POINTER(fc_entries);
}

Datum gin_consistent_jsonb(PG_FUNCTION_ARGS)
{
	bool	   *fc_check = (bool *) PG_GETARG_POINTER(0);
	StrategyNumber fc_strategy = PG_GETARG_UINT16(1);

	/* Jsonb	   *query = PG_GETARG_JSONB_P(2); */
	int32		fc_nkeys = PG_GETARG_INT32(3);

	Pointer    *fc_extra_data = (Pointer *) PG_GETARG_POINTER(4);
	bool	   *fc_recheck = (bool *) PG_GETARG_POINTER(5);
	bool		fc_res = true;
	int32		fc_i;

	if (fc_strategy == JsonbContainsStrategyNumber)
	{
		/*
		 * 我们必须始终重新检查，因为我们无法通过索引判断匹配项的位置是否与查询
		 * 对象的结构相匹配。（即使我们可以，我们也必须担心哈希
		 * 键和索引未能区分键与字符串数组
		 * 元素。）然而，元组当然只有在包含所有查询键时才匹配。
		 */
		*fc_recheck = true;
		for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
		{
			if (!fc_check[fc_i])
			{
				fc_res = false;
				break;
			}
		}
	}
	else if (fc_strategy == JsonbExistsStrategyNumber)
	{
		/*
		 * 尽管该键在索引中肯定存在，但我们必须重新检查
		 * 因为（1）键可能被哈希，并且（2）索引匹配可能是针对不是位于 JSON 对象顶层的键。对于（1），
		 * 我们可以查看查询键以了解它是否被哈希，并且如果没有则不进行重新检查，但索引没有足够的信息来判断（2）。
		 */
		*fc_recheck = true;
		fc_res = true;
	}
	else if (fc_strategy == JsonbExistsAnyStrategyNumber)
	{
		/* 对于普通的存在情况，我们必须重新检查 */
		*fc_recheck = true;
		fc_res = true;
	}
	else if (fc_strategy == JsonbExistsAllStrategyNumber)
	{
		/* 对于普通的存在情况，我们必须重新检查 */
		*fc_recheck = true;
		/* ...但是除非所有的键都存在，否则我们可以说 "false" */
		for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
		{
			if (!fc_check[fc_i])
			{
				fc_res = false;
				break;
			}
		}
	}
	else if (fc_strategy == JsonbJsonpathPredicateStrategyNumber ||
			 fc_strategy == JsonbJsonpathExistsStrategyNumber)
	{
		*fc_recheck = true;

		if (fc_nkeys > 0)
		{
			Assert(fc_extra_data && fc_extra_data[0]);
			fc_res = fc_execute_jsp_gin_node((JsonPathGinNode *) fc_extra_data[0], fc_check,
									   false) != GIN_FALSE;
		}
	}
	else
		elog(ERROR, "unrecognized strategy number: %d", fc_strategy);

	PG_RETURN_BOOL(fc_res);
}

Datum gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
{
	GinTernaryValue *fc_check = (GinTernaryValue *) PG_GETARG_POINTER(0);
	StrategyNumber fc_strategy = PG_GETARG_UINT16(1);

	/* Jsonb	   *query = PG_GETARG_JSONB_P(2); */
	int32		fc_nkeys = PG_GETARG_INT32(3);
	Pointer    *fc_extra_data = (Pointer *) PG_GETARG_POINTER(4);
	GinTernaryValue fc_res = GIN_MAYBE;
	int32		fc_i;

	/*
	 * 注意，我们从不返回 GIN_TRUE，只返回 GIN_MAYBE 或 GIN_FALSE；这
	 * 与在常规一致性函数中始终强制重新检查相对应，原因在那儿列出。
	 */
	if (fc_strategy == JsonbContainsStrategyNumber ||
		fc_strategy == JsonbExistsAllStrategyNumber)
	{
		/* 所有提取的键必须存在 */
		for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
		{
			if (fc_check[fc_i] == GIN_FALSE)
			{
				fc_res = GIN_FALSE;
				break;
			}
		}
	}
	else if (fc_strategy == JsonbExistsStrategyNumber ||
			 fc_strategy == JsonbExistsAnyStrategyNumber)
	{
		/* 至少必须存在一个提取的键 */
		fc_res = GIN_FALSE;
		for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
		{
			if (fc_check[fc_i] == GIN_TRUE ||
				fc_check[fc_i] == GIN_MAYBE)
			{
				fc_res = GIN_MAYBE;
				break;
			}
		}
	}
	else if (fc_strategy == JsonbJsonpathPredicateStrategyNumber ||
			 fc_strategy == JsonbJsonpathExistsStrategyNumber)
	{
		if (fc_nkeys > 0)
		{
			Assert(fc_extra_data && fc_extra_data[0]);
			fc_res = fc_execute_jsp_gin_node((JsonPathGinNode *) fc_extra_data[0], fc_check,
									   true);

			/* 应始终重新检查结果 */
			if (fc_res == GIN_TRUE)
				fc_res = GIN_MAYBE;
		}
	}
	else
		elog(ERROR, "unrecognized strategy number: %d", fc_strategy);

	PG_RETURN_GIN_TERNARY_VALUE(fc_res);
}

/*
 *
 * jsonb_path_ops GIN 操作类支持函数
 *
 * 在 jsonb_path_ops 索引中，GIN 键是 uint32 哈希，每个 JSON
 * 值一个；但是导致每个值的 JSON 键也包含在它的
 * 哈希计算中。这意味着我们只能支持包含查询，
 * 但是索引可以区分例如 {"foo": 42} 和 {"bar": 42}
 * ，因为会生成不同的哈希。
 *
 */

Datum gin_extract_jsonb_path(PG_FUNCTION_ARGS)
{
	Jsonb	   *fc_jb = PG_GETARG_JSONB_P(0);
	int32	   *fc_nentries = (int32 *) PG_GETARG_POINTER(1);
	int			fc_total = JB_ROOT_COUNT(fc_jb);
	JsonbIterator *fc_it;
	JsonbValue	fc_v;
	JsonbIteratorToken fc_r;
	PathHashStack fc_tail;
	PathHashStack *fc_stack;
	GinEntries	fc_entries;

	/* 如果根级别为空，我们肯定没有键 */
	if (fc_total == 0)
	{
		*fc_nentries = 0;
		PG_RETURN_POINTER(NULL);
	}

	/* 否则，使用 2 * 根计数作为结果大小的初步估计 */
	fc_init_gin_entries(&fc_entries, 2 * fc_total);

	/* 我们保留一个与父键级别对应的部分哈希栈 */
	fc_tail.parent = NULL;
	fc_tail.hash = 0;
	fc_stack = &fc_tail;

	fc_it = JsonbIteratorInit(&fc_jb->root);

	while ((fc_r = JsonbIteratorNext(&fc_it, &fc_v, false)) != WJB_DONE)
	{
		PathHashStack *fc_parent;

		switch (fc_r)
		{
			case WJB_BEGIN_ARRAY:
			case WJB_BEGIN_OBJECT:
				/* 为这个对象推入一个栈级别 */
				fc_parent = fc_stack;
				fc_stack = (PathHashStack *) palloc(sizeof(PathHashStack));

				/*
				 * 我们从外部嵌套级别传递哈希，以便
				 * 嵌套值的哈希将包括外部键
				 * 以及它们自己的键。
				 *
				 * 将一个数组嵌套在另一个数组中不会改变
				 * 最内层标量元素的哈希值，但这似乎
				 * 并不重要。
				 */
				fc_stack->hash = fc_parent->hash;
				fc_stack->parent = fc_parent;
				break;
			case WJB_KEY:
				/* 将这个键混入当前外部哈希中 */
				JsonbHashScalarValue(&fc_v, &fc_stack->hash);
				/* 哈希现在准备好纳入值 */
				break;
			case WJB_ELEM:
			case WJB_VALUE:
				/* 将元素或值的哈希混入准备好的哈希中 */
				JsonbHashScalarValue(&fc_v, &fc_stack->hash);
				/* 并发出一个索引条目 */
				add_gin_entry(&fc_entries, UInt32GetDatum(fc_stack->hash));
				/* 为下一个键、值或子对象重置哈希 */
				fc_stack->hash = fc_stack->parent->hash;
				break;
			case WJB_END_ARRAY:
			case WJB_END_OBJECT:
				/* 弹出栈 */
				fc_parent = fc_stack->parent;
				pfree(fc_stack);
				fc_stack = fc_parent;
				/* 为下一个键、值或子对象重置哈希 */
				if (fc_stack->parent)
					fc_stack->hash = fc_stack->parent->hash;
				else
					fc_stack->hash = 0;
				break;
			default:
				elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) fc_r);
		}
	}

	*fc_nentries = fc_entries.count;

	PG_RETURN_POINTER(fc_entries.buf);
}

Datum gin_extract_jsonb_query_path(PG_FUNCTION_ARGS)
{
	int32	   *fc_nentries = (int32 *) PG_GETARG_POINTER(1);
	StrategyNumber fc_strategy = PG_GETARG_UINT16(2);
	int32	   *fc_searchMode = (int32 *) PG_GETARG_POINTER(6);
	Datum	   *fc_entries;

	if (fc_strategy == JsonbContainsStrategyNumber)
	{
		/* 查询是一个 jsonb，因此只需应用 gin_extract_jsonb_path ... */
		fc_entries = (Datum *)
			DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path,
												PG_GETARG_DATUM(0),
												PointerGetDatum(fc_nentries)));

		/* ... 尽管 "contains {}" 需要完全索引扫描 */
		if (*fc_nentries == 0)
			*fc_searchMode = GIN_SEARCH_MODE_ALL;
	}
	else if (fc_strategy == JsonbJsonpathPredicateStrategyNumber ||
			 fc_strategy == JsonbJsonpathExistsStrategyNumber)
	{
		JsonPath   *fc_jp = PG_GETARG_JSONPATH_P(0);
		Pointer   **fc_extra_data = (Pointer **) PG_GETARG_POINTER(4);

		fc_entries = fc_extract_jsp_query(fc_jp, fc_strategy, true, fc_nentries, fc_extra_data);

		if (!fc_entries)
			*fc_searchMode = GIN_SEARCH_MODE_ALL;
	}
	else
	{
		elog(ERROR, "unrecognized strategy number: %d", fc_strategy);
		fc_entries = NULL;
	}

	PG_RETURN_POINTER(fc_entries);
}

Datum gin_consistent_jsonb_path(PG_FUNCTION_ARGS)
{
	bool	   *fc_check = (bool *) PG_GETARG_POINTER(0);
	StrategyNumber fc_strategy = PG_GETARG_UINT16(1);

	/* Jsonb	   *query = PG_GETARG_JSONB_P(2); */
	int32		fc_nkeys = PG_GETARG_INT32(3);
	Pointer    *fc_extra_data = (Pointer *) PG_GETARG_POINTER(4);
	bool	   *fc_recheck = (bool *) PG_GETARG_POINTER(5);
	bool		fc_res = true;
	int32		fc_i;

	if (fc_strategy == JsonbContainsStrategyNumber)
	{
		/*
		 * jsonb_path_ops 必然是有损的，不仅是因为哈希
		 * 冲突，还因为它不保留 JSON 对象结构的完整
		 * 信息。此外，关于数组中原始标量的包含
		 * 存在一些特殊规则，这里没有处理。因此我们必须
		 * 始终重新检查匹配。然而，如果并非所有键都存在，
		 * 元组肯定不匹配。
		 */
		*fc_recheck = true;
		for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
		{
			if (!fc_check[fc_i])
			{
				fc_res = false;
				break;
			}
		}
	}
	else if (fc_strategy == JsonbJsonpathPredicateStrategyNumber ||
			 fc_strategy == JsonbJsonpathExistsStrategyNumber)
	{
		*fc_recheck = true;

		if (fc_nkeys > 0)
		{
			Assert(fc_extra_data && fc_extra_data[0]);
			fc_res = fc_execute_jsp_gin_node((JsonPathGinNode *) fc_extra_data[0], fc_check,
									   false) != GIN_FALSE;
		}
	}
	else
		elog(ERROR, "unrecognized strategy number: %d", fc_strategy);

	PG_RETURN_BOOL(fc_res);
}

Datum gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS)
{
	GinTernaryValue *fc_check = (GinTernaryValue *) PG_GETARG_POINTER(0);
	StrategyNumber fc_strategy = PG_GETARG_UINT16(1);

	/* Jsonb	   *query = PG_GETARG_JSONB_P(2); */
	int32		fc_nkeys = PG_GETARG_INT32(3);
	Pointer    *fc_extra_data = (Pointer *) PG_GETARG_POINTER(4);
	GinTernaryValue fc_res = GIN_MAYBE;
	int32		fc_i;

	if (fc_strategy == JsonbContainsStrategyNumber)
	{
		/*
		 * 注意，我们从不返回 GIN_TRUE，只返回 GIN_MAYBE 或 GIN_FALSE；
		 * 这与在常规一致性函数中始终强制重新检查相对应，原因在那儿列出。
		 */
		for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
		{
			if (fc_check[fc_i] == GIN_FALSE)
			{
				fc_res = GIN_FALSE;
				break;
			}
		}
	}
	else if (fc_strategy == JsonbJsonpathPredicateStrategyNumber ||
			 fc_strategy == JsonbJsonpathExistsStrategyNumber)
	{
		if (fc_nkeys > 0)
		{
			Assert(fc_extra_data && fc_extra_data[0]);
			fc_res = fc_execute_jsp_gin_node((JsonPathGinNode *) fc_extra_data[0], fc_check,
									   true);

			/* 应始终重新检查结果 */
			if (fc_res == GIN_TRUE)
				fc_res = GIN_MAYBE;
		}
	}
	else
		elog(ERROR, "unrecognized strategy number: %d", fc_strategy);

	PG_RETURN_GIN_TERNARY_VALUE(fc_res);
}

/*
 * 从标志字节和文本表示构造一个 jsonb_ops GIN 键
 * （不需要以 null 结束）。该函数负责对超长文本表示
 * 进行哈希；如果执行该操作，将把 JGINFLAG_HASHED 位
 * 添加到标志值中。
 */
static Datum fc_make_text_key(char fc_flag, const char *fc_str, int fc_len)
{
	text	   *fc_item;
	char		fc_hashbuf[10];

	if (fc_len > JGIN_MAXLENGTH)
	{
		uint32		fc_hashval;

		fc_hashval = DatumGetUInt32(hash_any((const unsigned char *) fc_str, fc_len));
		snprintf(fc_hashbuf, sizeof(fc_hashbuf), "%08x", fc_hashval);
		fc_str = fc_hashbuf;
		fc_len = 8;
		fc_flag |= JGINFLAG_HASHED;
	}

	/*
	 * 现在构建文本 Datum。为简单起见，我们在这里构建一个 4 字节头
	 * 的 varlena 文本 Datum，但我们预计它会在存储到索引时转换为
	 * 短头格式。
	 */
	fc_item = (text *) palloc(VARHDRSZ + fc_len + 1);
	SET_VARSIZE(fc_item, VARHDRSZ + fc_len + 1);

	*VARDATA(fc_item) = fc_flag;

	memcpy(VARDATA(fc_item) + 1, fc_str, fc_len);

	return PointerGetDatum(fc_item);
}

/*
 * 创建 JsonbValue 的文本表示形式，作为 jsonb_ops 索引中的 GIN
 * 键。如果 JsonbValue 是键，或者如果它是一个字符串数组元素
 * （因为我们假装这些是键，见 jsonb.h），则 is_key 为 true。
 */
static Datum fc_make_scalar_key(const JsonbValue *fc_scalarVal, bool fc_is_key)
{
	Datum		fc_item;
	char	   *fc_cstr;

	switch (fc_scalarVal->type)
	{
		case jbvNull:
			Assert(!fc_is_key);
			fc_item = fc_make_text_key(JGINFLAG_NULL, "", 0);
			break;
		case jbvBool:
			Assert(!fc_is_key);
			fc_item = fc_make_text_key(JGINFLAG_BOOL,
								 fc_scalarVal->val.boolean ? "t" : "f", 1);
			break;
		case jbvNumeric:
			Assert(!fc_is_key);

			/*
			 * 需要一种标准化的文本表示，避免尾随零，
			 * 以便数值相等的值将产生相等的字符串。
			 *
			 * 数值以相对笨重的文本格式存储并不理想。
			 * 然而，这是一种在 GIN B-树中存储“联合”类型的便利表示
			 * ，并且索引 Jsonb 字符串具有优先权。
			 */
			fc_cstr = numeric_normalize(fc_scalarVal->val.numeric);
			fc_item = fc_make_text_key(JGINFLAG_NUM, fc_cstr, strlen(fc_cstr));
			pfree(fc_cstr);
			break;
		case jbvString:
			fc_item = fc_make_text_key(fc_is_key ? JGINFLAG_KEY : JGINFLAG_STR,
								 fc_scalarVal->val.string.val,
								 fc_scalarVal->val.string.len);
			break;
		default:
			elog(ERROR, "unrecognized jsonb scalar type: %d", fc_scalarVal->type);
			fc_item = 0;			/* 保持编译器安静 */
			break;
	}

	return fc_item;
}
